package doc

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"regexp"
	"time"

	//not yet become standard
	"code.google.com/p/go.net/html"
)

//用于markdown的html
var mdTypes = []string{
	"p",
	"h1",
	"h2",
	"h3",
	"h4",
	"h5",
	"h6",
	"br",
	"hr",
	"b",
	"i",
	"code",
	"a",
	"img",
	"blockquote",
	"pre",
	"ul",
	"ol",
	"li",
	//to keep complete html construction
	//TODO:convert to appropriate markdown
	//"html",
	"textarea",
	"th",
	"tr",
	"td",
	"tbody",
	"thead",
	"table",
}

type replaceFunc func(string, []string) (string, error)

//form DOM tree
//构造DOM树,
//param html
//返回document root
//return RootNode and err
func parse(input []byte) (*html.Node, error) {
	n, err := html.Parse(bytes.NewBuffer(input))
	/*
		for ; n.Type != html.DocumentNode && n != nil; n = n.Parent {
		}
	*/
	return n, err
}

//鉴定tag是否是需要解析成md的
//if it is worh convertting to markdwon,or it will be moved from DOM tree
//return yes or no
func isMdType(n *html.Node) bool {
	var flag bool
	tag := n.Data
	for _, v := range mdTypes {
		if tag == v {
			flag = true
			break
		}
	}
	if n.Type == html.TextNode {
		flag = true
	}
	return flag
}

//remove node
//if node has children , insert children among siblings
//else just link its siblings
//params *Node n
//return next node to visit for DFS
func removeNode(n *html.Node) (next *html.Node) {
	//if n is invalid, just return
	if n == nil {
		return
	}
	// node is only child of parent
	if n.PrevSibling == nil && n.NextSibling == nil {
		if n.Parent != nil {
			//if n has children , give them to parent
			n.Parent.FirstChild = n.FirstChild
			n.Parent.LastChild = n.LastChild
			for c := n.FirstChild; c != nil; c = c.NextSibling {
				c.Parent = n.Parent
			}
		}
		//if has child visit first child
		if n.FirstChild != nil {
			next = n.FirstChild
		}
		n = nil
		return next
	}
	//node is first child and has sibling
	if n.PrevSibling == nil && n.NextSibling != nil {
		if n.Parent != nil {
			//if n has children
			//判断是否有子节点,这个判断是否有点多,不确定
			if n.FirstChild != nil && n.LastChild != nil {
				n.Parent.FirstChild = n.FirstChild
				n.NextSibling.PrevSibling = n.LastChild
				n.LastChild.NextSibling = n.NextSibling
				for c := n.FirstChild; c != nil; c = c.NextSibling {
					c.Parent = n.Parent
				}
				next = n.Parent.FirstChild
				n = nil
				return next
			} else {
				n.Parent.FirstChild = n.NextSibling
				n.NextSibling.PrevSibling = nil
				next = n.Parent.FirstChild
				n = nil
				return next
			}
		} else {
			panic("n parent is nil")
		}
	}
	//node is last child and has sibling
	if n.NextSibling == nil && n.PrevSibling != nil {
		if n.Parent != nil {
			//if n has children
			if n.LastChild != nil && n.FirstChild != nil {
				n.Parent.LastChild = n.LastChild
				for c := n.LastChild; c != nil; c = c.PrevSibling {
					c.Parent = n.Parent
				}
				n.PrevSibling.NextSibling = n.FirstChild
				n.FirstChild.PrevSibling = n.PrevSibling
				next = n.FirstChild
				n = nil
				return next
			} else {
				n.Parent.LastChild = n.PrevSibling
				n.PrevSibling.NextSibling = nil
				next = n.Parent.NextSibling
				fmt.Println("parent nextsibling ", next)
				n = nil
				//if n has no child just return nil

				return next
			}
		} else {
			panic("n parent is nil")
		}
	}
	//node is in the middle of children
	if n.NextSibling != nil && n.PrevSibling != nil {
		if n.Parent != nil {
			//if n has children
			if n.LastChild != nil && n.FirstChild != nil {
				n.PrevSibling.NextSibling = n.FirstChild
				n.FirstChild.PrevSibling = n.PrevSibling
				n.NextSibling.PrevSibling = n.LastChild
				n.LastChild.NextSibling = n.NextSibling
				for c := n.FirstChild; c != nil; c = c.NextSibling {
					c.Parent = n.Parent
				}
				next = n.FirstChild
				n = nil
				return next
			} else {
				n.PrevSibling.NextSibling = n.NextSibling
				n.NextSibling.PrevSibling = n.PrevSibling
				next = n.NextSibling
				n = nil
				return next
			}
		} else {
			panic("n parent is ni")
		}
	}
	//nothing matched return nil
	return nil
}

//只留下Md的元素来构成一个DOM树
//DFS 遍历
//深度优先遍历好像不好处理
//如果是广度优先遍历的话比较好理解
//in DSF order visit DOM tree and move node that is not markdown type
//param root no
func pureMdDom(n *html.Node) {
	if n == nil {
		return
	}
	var c *html.Node
	c = n.FirstChild
	if n.Type == html.ElementNode || n.Type == html.TextNode {
		if !isMdType(n) {
			c = removeNode(n)
		}
	} else if n.Type != html.DocumentNode {
		c = removeNode(n)
	}
	for ; c != nil; c = c.NextSibling {
		pureMdDom(c)
	}
}

//render to out with pure markdown-type-only HTML after pureDdDom

func render(out io.Writer, root *html.Node) error {
	//pureMdDom(root)
	if root.Type != html.DocumentNode {
		return errors.New("root Type is not document Node,this version is pnly support well-fromed HTML")
	} else {
		return html.Render(out, root)
	}
}

//use render to convert html to pure MdOnly document
//params html entire html
//return converted document
//return error

func renderString(html string) string {
	buf := new(bytes.Buffer)
	//如果是 var buf = bytes.Buffer
	//write的时候修改的不是本身
	//要用指针才可以
	html = remove(html, "script")
	html = remove(html, "style")
	n, err := parse([]byte(html))
	pureMdDom(n)
	if err != nil {
		panic(err)
	}
	render(buf, n)
	return fmt.Sprintf("%s\n", buf.Bytes())

}

var (
	//20 秒连接, 5 秒IO时间
	client = clientConstructor(20, 25)
)

//以秒为单位,设置连接时间,和连接+读取总时间的超时
//return http.clien
func clientConstructor(connectTimeOut int, totalTimeOut int) *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				deadline := time.Now().Add(time.Second * time.Duration(totalTimeOut))
				c, err := net.DialTimeout(network, addr, time.Second*time.Duration(connectTimeOut))
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}
}

func WebRender(url string) string {
	res, err := client.Get(url)
	if err != nil {
		fmt.Println(err)
		return ""
	}
	defer res.Body.Close()
	html, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Println(err)
		return ""
	}
	return ToMarkdown(renderString(string(html)))
}

//public mathod
func Render(html string) string {
	html = renderString(html)
	return ToMarkdown(html)
}

//清楚标签
//param content html
//param tag html tag
//return tag-removed html
func remove(content, tag string) string {
	return regexp.MustCompile("<"+tag+"[\\s\\S]+?</"+tag+">").ReplaceAllString(content, "")

}
